
AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "tables.lua" )
AddCSLuaFile( "admin.lua" )
AddCSLuaFile( "shared.lua" )
AddCSLuaFile( "cl_postprocess.lua" )
AddCSLuaFile( "cl_spawnmenu.lua" )
AddCSLuaFile( "cl_scoreboard.lua" )
AddCSLuaFile( "cl_hudpickup.lua" )
AddCSLuaFile( "cl_deathnotice.lua" )
AddCSLuaFile( "ply_extension.lua" )

include( "tables.lua" )
include( "admin.lua" )
include( "shared.lua" )
include( "player.lua" )
include( "ply_extension.lua" )

/*---------------------------------------------------------
---------------------------------------------------------*/
function GM:Initialize( )

	self.BaseClass:Initialize( )
	self:SetupTeams()

	game.ConsoleCommand("sv_maxvelocity 9999\n")
	game.ConsoleCommand("mapcyclefile "..GameInfo.CycleFile.."\n")
 
	TEAM_RED = 1
	TEAM_BLUE = 2

	GameInfo.RoundsDone = 0
	GameInfo.GameOver = false
	
	GameInfo.RedScore = 0
	GameInfo.BlueScore = 0
	
	if GameInfo.RealisticFallDamage then
		game.ConsoleCommand("mp_falldamage 1\n")
	end
	
end

function GM:Think()
	for k,v in pairs(player.GetAll()) do
		v:DoOnFire()
	end
end

function GM:InitPostEntity( )

	GameInfo.BeaconPositions = {}
	GameInfo.BeaconTeams = {}
	for k,v in pairs(ents.FindByClass("sent_beacon")) do
		table.insert(GameInfo.BeaconPositions,v:GetPos())
	end
	for k,v in pairs(ents.FindByClass("sent_beacon")) do
		table.insert(GameInfo.BeaconTeams,v:GetTeam())
	end
end

function GM:PlayerInitialSpawn( ply )
	self.BaseClass:PlayerInitialSpawn( ply )
	ply:PickTeam()
	ply:SetScore(0)
	local prof = OpenFromFile(string.sub(ply:SteamID(),11))
	if prof != nil then
		ply.Class = prof[1] or "Soldier"
		ply.Vehicle = {}
		ply.Vehicle.Land = {}
		ply.Vehicle.Land.Wheels = prof[2] or "Tyre"
		ply.Vehicle.Land.Torso = prof[3] or "Maytag"
		ply.Vehicle.Land.Weapon = prof[4] or "Machinegun"
		ply.Vehicle.Air = {}
		ply.Vehicle.Air.Wings = prof[5] or "Plank"
		ply.Vehicle.Air.Torso = prof[6] or "Woody"
		ply.Vehicle.Air.Weapon = prof[7] or "Chaingun"
		ply.Invert = prof[8] or true
	else
		ply:ConCommand("gm_showspare1")
		ply.Invert = false
		ply.Vehicle = nil
		ply.Class = "Soldier"
		ply.Vehicle = {}
		ply.Vehicle.Land = {}
		ply.Vehicle.Land.Wheels = "Tyre"
		ply.Vehicle.Land.Torso = "Maytag"
		ply.Vehicle.Land.Weapon = "Machinegun"
		ply.Vehicle.Air = {}
		ply.Vehicle.Air.Wings = "Plank"
		ply.Vehicle.Air.Torso = "Woody"
		ply.Vehicle.Air.Weapon = "Chaingun"
	end
	ply:SetNetworkedInt("beaconhealth2",GameInfo.BeaconHealth)
	ply:SetNetworkedInt("beaconhealth1",GameInfo.BeaconHealth)
	ply:SetNetworkedInt("maxbeaconhealth",GameInfo.BeaconHealth)
	
	ply.ViewOffset = 40
	ply.IsInVehicle = false
	ply.CurVehicle = nil
	ply.ClassPickTime = 0
	
	umsg.Start("ClassUpdate", ply)
	umsg.String( ply.Class )
	umsg.End() 
	umsg.Start("UpdateRedScore",ply)
	umsg.Short(GameInfo.RedScore)
	umsg.End()
	umsg.Start("UpdateBlueScore",ply)
	umsg.Short(GameInfo.BlueScore)
	umsg.End()
end

function GM:PlayerLoadout(ply) 
end

function GM:PlayerSetup( ply )
	if ply.IsInVehicle and ply.CurVehicle then //They are exiting a vehicle
		ply:StripWeapons()
		for i=1,#ClassTypes do
			if (string.lower(ClassTypes[i].Name) == string.lower(ply.Class)) then
				GAMEMODE:SetPlayerSpeed( ply, ClassTypes[i].Speed, ClassTypes[i].Speed )
				ply:Give(ClassTypes[i].Primary)
				ply:Give("weapon_phonecam")
				ply:SetModel(ClassTypes[i].Model)
				ply:SetHealth(ply.CurHealth)
			end
		end
	if ply:Team() == TEAM_RED then
		ply.Color = Color(255,200,200,255)
	else
		ply.Color = Color(200,200,255,255)
	end
	ply:SetColor(ply.Color)
	return
	end
	
	for i=1,#ClassTypes do
		if (string.lower(ClassTypes[i].Name) == string.lower(ply.Class)) then
			GAMEMODE:SetPlayerSpeed( ply, ClassTypes[i].Speed, ClassTypes[i].Speed )
			ply:Give(ClassTypes[i].Primary)
			ply:Give("weapon_phonecam")
			ply:SetModel(ClassTypes[i].Model)
			ply:SetHealth(ClassTypes[i].Health)
			ply.Death = ClassTypes[i].Death
			ply.Pain = ClassTypes[i].Pain
			ply.Taunt = ClassTypes[i].Taunt
			ply.DeathType = "normal"
		end
		umsg.Start("ClassUpdate", ply)
		umsg.String( ply.Class )
		umsg.End() 
	end
	if ply:Team() == TEAM_RED then
		ply.Color = Color(255,200,200,255)
	else
		ply.Color = Color(200,200,255,255)
	end
	ply:SetColor(ply.Color)
end

function GM:PlayerSpawn( ply )
	ply:ShouldDropWeapon(false)
	GAMEMODE:PlayerSetup(ply)	
end

function GM:ShowHelp(ply)
	umsg.Start("ShowHelp", ply)
	umsg.Short( 5 )
	umsg.End() 
end

function GM:ShowSpare1(ply)
	if ply.ClassPickTime > CurTime() then ply:ChatPrint("You must wait a while before you can change classes again.") return end
	umsg.Start("ClassMenu", ply)
	umsg.Short( 5 )
	umsg.End() 
	ply.ClassPickTime = CurTime() + 20
end

function GM:ShowSpare2(ply)
	if ply:GetModel() != "models/player/barney.mdl" and ply:GetModel() != "models/player/combine_soldier.mdl" then ply:ChatPrint("Your class cannot build vehicles.") return end 
	local tbl = ents.FindInSphere(ply:GetPos(),2000)
	for k,v in pairs(tbl) do
		if v:GetClass() == "sent_vehiclepad" then
			if v:GetTeam() == ply:Team() then
				if ply.IsInVehicle == false and not ply.MenuOpen then
					ply:SetEyeAngles( ply:GetForward() )
					ply:Lock()
					ply:GodEnable()
					umsg.Start("VehicleMenu", ply)
					umsg.Short( 5 )
					umsg.End() 
					ply.MenuOpen = true
					return
				end
			end
		end
	end
	ply:ChatPrint("You aren't close enough to your team's vehicle pad.")
end

function GM:ShowTeam(ply)
	ply:ChooseTeam()
	ply:Spawn()
end

function GM:PlayerDisconnected(ply)
	ply:CleanupVehicle(true)
end

function GM:PlayerDeathSound()
	return true
end

function GM:DoPlayerDeath(ply, attacker, dmginfo)

	timer.Simple(10,GAMEMODE.ForceSpawn, GAMEMODE, ply)

	ply:Flashlight(false)
	ply:AddDeaths(1)
	
	if ply:GetFireTime() > 0 then
		ply.Death = GameSounds.Burn
	end
	
	ply:DoExtinguish()
	
	if not ply.IsInVehicle then //Normal death voice - normal player
		ply:EmitSound(Sound(ChooseRandom(ply.Death)),100,math.random(95,105))
	end
	
	if ply.IsInVehicle and ((attacker == nil) or (attacker == ply)) then //Suicide
		ply:ExitVehicle()
		ply.DeathType = "explosion"
		ply:CleanupVehicle(true)
	end
	
	if ply:GetVehicle() and not ply.IsInVehicle then //Remove their empty vehicle
		ply:CleanupVehicle(true)
	end
	
		//All we need is to spawn the corpse/effects.
		local ent = ents.Create( "prop_ragdoll" )
		if ent:IsValid() then
			local dtype = ply.DeathType
			if dtype == "explosion" then
				ent:SetModel(ChooseRandom(PlayerGibs))
			elseif dtype == "burn" then
				ent:SetModel(PlayerGibs[1]) //Charple
				ply:SetNetworkedEntity("burncorpse",ent)
				timer.Simple(10,ply.SetNetworkedEntity,ply,"burncorpse",nil)
			else
				ent:SetModel(ply:GetModel())
			end
			ent:SetKeyValue("origin", ply:GetPos().x.." "..ply:GetPos().y.." "..ply:GetPos().z + 1)
			ent:SetKeyValue("angles", ply:GetAngles().p + math.random(-3,3).." "..ply:GetAngles().y +  math.random(-3,3).." "..ply:GetAngles().r + math.random(-3,3))
			ent:Spawn()
			ent:Activate() 
			ent:SetCollisionGroup( COLLISION_GROUP_WEAPON )
			ent:Fire("kill",1,math.Clamp(GameInfo.RagdollDieTime,10,90))
			ply.CorpseEnt = ent
			ply:SetNetworkedEntity("corpse",ply.CorpseEnt)
			
			local phys = ent:GetPhysicsObject()
			if phys:IsValid() then
				phys:SetMass(50)
			end
				
			local fnum
			if dmginfo:IsExplosionDamage() or dtype == "explosion" then 
				fnum = phys:GetMass()^3
			else
				fnum = phys:GetMass()^2
			end
				
			if attacker != ply and attacker != nil and attacker:IsPlayer() then
				local dir = (ent:GetPos() - attacker:GetPos()):Normalize()
				phys:ApplyForceCenter(attacker:GetAimVector() * fnum) //(dir * fnum)
				phys:AddAngleVelocity(Angle(math.random(-1*(fnum/3),fnum/3),math.random(-1*(fnum/2),fnum/2),math.random(-1*(fnum/3),fnum/3)))
				
				if attacker:IsPlayer() then
				attacker:AddFrags(1)
				local taunt = math.random(1,4)
					if taunt == 4 and attacker:Alive() then
						timer.Simple(.9,attacker.EmitSound,attacker,Sound(ChooseRandom(attacker.Taunt)),100,math.random(95,105))
					end
				end
			else
				local dir = VectorRand():Normalize()
				local vel = ply:GetVelocity()
				phys:ApplyForceCenter((dir * (fnum/3)) + vel)
				local ang = Angle(math.random(-1*(fnum/4),fnum/4),math.random(-1*(fnum/4),fnum/4),math.random(-1*(fnum/4),fnum/4))
				phys:AddAngleVelocity(ang)
			end
		end
		
end

function GM:PlayerHurt( ply, attacker, hp, dmg ) 
	if ply.IsInVehicle then ply:SetHealth(hp+dmg) end
end

function GM:KeyPress( ply, key )
	
	if ply:GetVehicle() and ply.IsInVehicle then
		if (key == IN_USE) and ply:GetVehicle():IsValid() then
			ply:ExitVehicle()
		end
	end

	if (key == IN_ATTACK2) and ply.IsInVehicle then
		ply.ViewOffset = ply.ViewOffset or 10
		ply.ViewOffset = ply.ViewOffset + 5
		if ply.ViewOffset > 45 then
			ply.ViewOffset = 10
		end
		umsg.Start("ViewOffset",ply)
		umsg.Short(ply.ViewOffset)
		umsg.End()
	end
	
	if (key == IN_USE) then
		local t = util.GetPlayerTrace(ply)
		local tr = util.TraceLine(t)
		
		if tr.Entity == nil or tr.Entity == NULL then return end
		
		if tr.Entity:GetClass() == "func_button" then
			local strt = ""..ply:Team()..""
			local name = tr.Entity:GetName()
			if strt == name and ply:GetPos():Distance(tr.HitPos) < 250 then
				if ply.Class == "Pilot" or ply.Class == "Driver" then
					if ply.CurVehicle == nil then
						ply:EmitSound(Sound("buttons/button9.wav"),100,math.random(90,110))
						ply:BuildVehicle()
					end
				else
					ply:ChatPrint("Your class is not allowed to spawn vehicles.")
				end
			end
		elseif ply:GetPos():Distance(tr.HitPos) < 250 and (string.find(tr.Entity:GetClass(),"vehicle_land") or string.find(tr.Entity:GetClass(),"vehicle_air")) then
			if (tr.Entity == ply.CurVehicle) then
				tr.Entity:SetAngles(tr.Entity.Offset)
				ply:EnterVehicle(tr.Entity)
				tr.Entity:SetDriver(ply)
			end
		end
	end
end

function GM:ForceSpawn(ply)
	if !ply:IsValid() or ply:Alive() then return end
	ply:Spawn()
end

function GM:CanPlayerSuicide( ply )
	ply:ConCommand("name FAGGOTBREATH")
	return true
end 

function UpdateAirVehicleTorso(ply,cmd,args)
	if args[2] then args[1] = args[1].." "..args[2] end
	ply.Vehicle.Air.Torso = args[1]
	ply.MenuOpen = false
end	
function UpdateAirVehicleWings(ply,cmd,args)
	if args[2] then args[1] = args[1].." "..args[2] end
	ply.Vehicle.Air.Wings = args[1]
end	
function UpdateAirVehicleWeapon(ply,cmd,args)
	if args[2] then args[1] = args[1].." "..args[2] end
	ply.Vehicle.Air.Weapon = args[1]
	ply:UnLock()
end	
concommand.Add("airvehicle_upd_torso", UpdateAirVehicleTorso)		
concommand.Add("airvehicle_upd_wings", UpdateAirVehicleWings)	
concommand.Add("airvehicle_upd_weapon", UpdateAirVehicleWeapon)		

function UpdateLandVehicleTorso(ply,cmd,args)
	if args[2] then args[1] = args[1].." "..args[2] end
	ply.Vehicle.Land.Torso = args[1]
	ply.MenuOpen = false
end	
function UpdateLandVehicleWheels(ply,cmd,args)
	if args[2] then args[1] = args[1].." "..args[2] end
	ply.Vehicle.Land.Wheels = args[1]
end	
function UpdateLandVehicleWeapon(ply,cmd,args)
	if args[2] then args[1] = args[1].." "..args[2] end
	ply.Vehicle.Land.Weapon = args[1]
	ply:UnLock()
end	
concommand.Add("landvehicle_upd_torso", UpdateLandVehicleTorso)		
concommand.Add("landvehicle_upd_wheels", UpdateLandVehicleWheels)	
concommand.Add("landvehicle_upd_weapon", UpdateLandVehicleWeapon)		
	
function ChangeClass(ply,cmd,args)
	if args[1] == nil then return end
	ply.Class = args[1]
	ply:ChatPrint("Your class modifications will be applied when you respawn.")
	ply:EmitSound(Sound("weapons/pistol/pistol_reload1.wav"))
	local tbl = {ply.Class,
	ply.Vehicle.Land.Wheels,
	ply.Vehicle.Land.Torso,
	ply.Vehicle.Land.Weapon,
	ply.Vehicle.Air.Wings,
	ply.Vehicle.Air.Torso,
	ply.Vehicle.Air.Weapon,
	ply.Invert}
	WriteToFile(string.sub(ply:SteamID(),11),tbl)
end
concommand.Add("changeclass", ChangeClass)

function InvertControls(ply,cmd,args)
	if ply.Invert then
		ply.Invert = not ply.Invert
	end
end
concommand.Add("tv_invert", InvertControls)

function WriteToFile(ID,tbl)
	local Name = "terminalvelocity/"..ID..".txt"
	local str
	for k,v in pairs(tbl) do
		if str == nil then
			str = tostring(v)
		else
			str = str.."~"..tostring(v)
		end
	end
	file.Write(Name, str)
	//file.Write(Name, table.concat(tbl,"~"))
end

function OpenFromFile(ID)
	local loaded = {}
	local Name = "terminalvelocity/"..ID..".txt"
	if file.Exists(Name) then
		loaded = string.Explode("~",file.Read(Name))
	end
return loaded end //returns a Table!

function ChooseRandom(tablename)
	local tnum = table.Count(tablename)
	local rand = math.random(1,tnum)
return tablename[rand] end

--[[function GM:PropsThink()
	for k,v in pairs(team.GetPlayers(TEAM_PROPS)) do
		
		if v:Alive() then
		
		local prop = v:GetProp()
		if (prop == nil) or !prop:IsValid() then v:PropCleanup() v:Kill() end
		local phys = prop:GetPhysicsObject()
		if !phys:IsValid() or (phys == nil) then v:Kill() end
		
		if (v:KeyDown(IN_RUN) or v:KeyDown(IN_SPEED)) then
			if string.lower(v.Ability) == "speed" then
				if v.ReloadTime < CurTime() and v:Alive() then
					local forward = v:GetAimVector() * phys:GetMass()^2.8
					phys:SetVelocity(forward+phys:GetVelocity()) 
					v:EmitSound(ChooseRandom(PropSprint),100,math.random(130,150))
					v.ReloadTime = CurTime() + v.Reload
					v:SetNetworkedFloat( "abilitytime", v.ReloadTime )
					prop:SetPhysicsAttacker(v)
				end
			elseif string.lower(v.Ability) == "stealth" then
				if v.ReloadTime < CurTime() and v:Alive() then
					v:BecomeStealth()
					v:EmitSound(Sound("weapons/physcannon/energy_bounce2.wav"),100,math.random(110,130))
					v.ReloadTime = CurTime() + v.Reload
					v:SetNetworkedFloat( "abilitytime", v.ReloadTime )
					prop:SetPhysicsAttacker(v)
				end
			elseif string.lower(v.Ability) == "regen" then
				if v.ReloadTime < CurTime() and v:Alive() then
					v:GainHealth()
					v:EmitSound(Sound("ambient/energy/whiteflash.wav"),100,math.random(110,140))
					v.ReloadTime = CurTime() + v.Reload
					v:SetNetworkedFloat( "abilitytime", v.ReloadTime )
					prop:SetPhysicsAttacker(v)
				end
			elseif string.lower(v.Ability) == "frost" then
				if v.ReloadTime < CurTime() and v:Alive() then
					v:Frost()
					v:EmitSound(ChooseRandom(IceCloud),100,math.random(130,150))
					v:EmitSound(ChooseRandom(IceForm),100,math.random(85,115))
					v.ReloadTime = CurTime() + v.Reload
					v:SetNetworkedFloat( "abilitytime", v.ReloadTime )
					prop:SetPhysicsAttacker(v)
				end
			elseif string.lower(v.Ability) == "incendiary" then
				if v.ReloadTime < CurTime() and v:Alive() then
					v:SuicideBomb()
					v:EmitSound(ChooseRandom(PropBomb),100,math.random(85,115))
					v.ReloadTime = CurTime() + v.Reload
					v:SetNetworkedFloat( "abilitytime", v.ReloadTime )
					prop:SetPhysicsAttacker(v)
				end
			end
		end
	
		if ( v:KeyDown( IN_FORWARD ) ) then
			if !phys:IsValid() or (phys == nil) then v:Kill() return end
			local forward = (v:GetAimVector() * v.Speed * phys:GetMass()) + Vector(0,0,20)
			phys:ApplyForceCenter(forward) 
		elseif ( v:KeyDown( IN_BACK ) ) then
			if !phys:IsValid() or (phys == nil) then v:Kill() return end
			local back = (v:GetAimVector() * (v.Speed * -1) * phys:GetMass()) + Vector(0,0,20)
			phys:ApplyForceCenter(back)
		elseif ( v:KeyDown( IN_MOVELEFT ) ) then
			if !phys:IsValid() or (phys == nil) then v:Kill() return end
			local left = ((v:GetAimVector():Angle():Right() * -1) * v.Speed * phys:GetMass()) + Vector(0,0,20)
			phys:ApplyForceCenter(left)
		elseif ( v:KeyDown( IN_MOVERIGHT ) ) then
			if !phys:IsValid() or (phys == nil) then v:Kill() return end
			local right = (v:GetAimVector():Angle():Right() * v.Speed * phys:GetMass()) + Vector(0,0,20)
			phys:ApplyForceCenter(right)
		elseif ( v:KeyDown( IN_JUMP ) ) then
			if !phys:IsValid() or (phys == nil) then v:Kill() return end
			local up = (v:GetUp() * v.Speed * phys:GetMass()) + Vector(0,0,20)
			phys:ApplyForceCenter(up)
		end
	end
end
end
hook.Add("Think", "PropThinkFunction", GM.PropsThink)  ]]

--[[function GM:PropBreak(breaker,prop)
	if !breaker:IsValid() or breaker == nil then
		if prop.Owner != nil and prop:IsValid() then
			if prop.Owner.Override == true then return end
			prop.Owner:AddDeaths(1)
		end
	end
end]]

--[[function GM:EntityTakeDamage( ent, inflictor, attacker, amount )

	if !ent:IsPlayer() and attacker:IsPlayer() then
		if ent.Owner != nil then 
			if ent.Owner:GetPropHealth() != nil then
			
				//if ent.Owner.Override == true then return end
				ent:SetHealth(9000)
				ent.Owner:SetPropHealth(ent.Owner:GetPropHealth() - amount)
				ent.LastDamager = attacker
				//Msg(attacker:Name().."\n")
				ent.Owner:SetNetworkedEntity( "lastdamager", attacker )
				ent:SetPhysicsAttacker(ent.Owner)
				
				local pain = ChooseRandom(PropHit)
				util.PrecacheSound(pain)
				ent:EmitSound(pain,math.random(80,100),math.random(90,110))
				ent.PainCounter = ent.PainCounter or 2
				ent.PainCounter = (ent.PainCounter-1)
				
				local num = math.random(1,9)
				if num == 6 and ent.PainCounter <= 0 then
					local effectdata = EffectData()
					effectdata:SetOrigin(ent:GetPos())
					util.Effect("prop_gib", effectdata)
					pain = ChooseRandom(PropPain)
					util.PrecacheSound(pain)
					ent:EmitSound(pain,math.random(100,110),math.random(130,140))
					ent.PainCounter = math.random(3,6)
				end
				
				if string.lower(ent.Owner.Ability) == "stealth" and ent:GetMaterial() == "sprites/heatwave" then
					timer.Simple(.1,GAMEMODE.ResetMaterial, GAMEMODE, ent)
				end
				
				if ent.Owner:GetPropHealth() <= 0 and ent.DieOnlyOnce == nil then
					ent.DieOnlyOnce = true
					ent.Owner:PropDeath()
				end
			end
		end
	end
end ]]